package icu.zhhll.lp;

import icu.zhhll.lp.strategy.LPStrategy;
import org.apache.commons.lang3.StringUtils;
import org.ojalgo.optimisation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * LP求解器
 *
 * @author zh
 * @date 2023/9/18 10:53
 */
public class LpSolver {
    private static final Logger LOGGER = LoggerFactory.getLogger("lp");
    private static final String DEFAULT_EXP_NAME = "exp";
    private final ExpressionsBasedModel model;

    // 基础模型，检验基础规则是否冲突
    private final ExpressionsBasedModel baseModel;
    // 有效变量索引
    private final Map<String, Integer> nameIndex = new HashMap<>();
    private final Map<Integer, String> indexName = new HashMap<>();
    private final Map<String, Integer> validNameIndex = new HashMap<>();
    // 无效变量索引
    private final Map<String, Integer> inValidNameIndex = new HashMap<>();
    // 存储表达式
    private final Map<String, Map<String, LpExpression>> lpExpressionMap = new LinkedHashMap<>();
    private final LPStrategy lpStrategy;
    private final String lpName;
    // 表达式数量
    private int expCount = 0;
    private int curVarIndex = 0;
    // 无效参数的数量
    private int inValidCount = 0;
    // 有效参数的数量
    private int validCount = 0;

    public LpSolver(String lpName, LPStrategy lpStrategy) {

        model = new ExpressionsBasedModel();
        baseModel = new ExpressionsBasedModel();


//            model.options.solution=NumberContext.ofScale(0).withMode(RoundingMode.HALF_DOWN);
//            model.options.time_abort= CalendarDateUnit.HOUR.toDurationInMillis();
//            model.options.time_suffice= CalendarDateUnit.MINUTE.toDurationInMillis()*30;
//            model.options.debug(IntegerSolver.class);
//            model.options.progress(IntegerSolver.class);
//            model.options.print=NumberContext.ofScale(0);
//            IntegerStrategy.ConfigurableStrategy strategy = (IntegerStrategy.ConfigurableStrategy) model.options.integer();
//            strategy = strategy.withGapTolerance(NumberContext.of(0, 0));
//            model.options.integer(strategy);
        this.lpStrategy = lpStrategy;
        this.lpName = lpName;
    }

    public String getLpName() {
        return lpName;
    }

    public ExpressionsBasedModel getModel() {
        return model;
    }

    public ExpressionsBasedModel getBaseModel() {
        return baseModel;
    }

    public int getExpCount() {
        return expCount;
    }

    public int getCurVarIndex() {
        return curVarIndex;
    }

    public Map<String, Integer> getNameIndex() {
        return nameIndex;
    }

    public Map<Integer, String> getIndexName() {
        return indexName;
    }

    public Map<String, Integer> getValidNameIndex() {
        return validNameIndex;
    }

    public Map<String, Integer> getInValidNameIndex() {
        return inValidNameIndex;
    }

    public Map<String, Map<String, LpExpression>> getLpExpressionMap() {
        return lpExpressionMap;
    }

    public LPStrategy getLpStrategy() {
        return lpStrategy;
    }

    public int getInValidCount() {
        return inValidCount;
    }

    public int getValidCount() {
        return validCount;
    }

    /**
     * 添加变量
     *
     * @param name
     * @param weight
     */
    public void addValidVariable(String name, double weight) {
        Variable variable = createVariable(name, VarType.NONE);
        variable.weight(weight);
        addValidVariable(name, variable);
    }

    public void addValidVariable(String name, double weight, VarType type) {
        Variable variable = createVariable(name, type);
        variable.weight(weight);
        addValidVariable(name, variable);

    }

    // 无效的参数，权重为0
    public void addInValidVariable(String name, VarType type, long upperLimit, long lowerLimit) {
        Variable variable = createVariable(name, type);
        variable.weight(0);
        variable.lower(lowerLimit);
        variable.upper(upperLimit);
        addValidVariable(name, variable, false);
    }

    // 无效的参数，权重为0
    public void addInValidVariable(String name, VarType type) {
        Variable variable = createVariable(name, type);
        variable.weight(0);
        addValidVariable(name, variable, false);
    }

    public void addValidVariable(String name, double weight, VarType type, long upperLimit, long lowerLimit) {
        Variable variable = createVariable(name, type);
        variable.weight(weight);
        variable.lower(lowerLimit);
        variable.upper(upperLimit);
        addValidVariable(name, variable);
    }

    public void addValidVariable(String name, double weight, VarType type, long lowerLimit) {
        Variable variable = createVariable(name, type);
        variable.weight(weight);
        variable.lower(lowerLimit);
        addValidVariable(name, variable);
    }

    private Variable createVariable(String name, VarType type) {
        Variable variable;
        if (type == VarType.BIN) {
            variable = Variable.makeBinary(name);
        } else if (type == VarType.INT) {
            variable = Variable.makeInteger(name);
        } else {
            variable = Variable.make(name);
        }
        return variable;
    }

    private void addValidVariable(String name, Variable variable) {
        addValidVariable(name, variable, true);
    }

    private void addValidVariable(String name, Variable variable, boolean isValid) {
        if (isValid) { // 有效的
            if (inValidCount != 0) {
                throw new LpParamException("无效参数应该在有效参数之前设置");
            }
            validCount++;
        } else {
            inValidCount++;
        }
        model.addVariable(variable);
        baseModel.addVariable(variable);
        indexName.put(curVarIndex, name);
        nameIndex.put(name, curVarIndex++);
    }

    public void addExpression(String namePre, LpExpression lpExpression) {

        // 默认非基础规则
        addExpression(namePre, lpExpression, false);

    }

    public void addExpression(String namePre, LpExpression lpExpression, boolean base) {
        List<LpParam> params = lpExpression.getParams();
        if (params == null || params.size() == 0) {
            throw new LpParamException("表达式错误，没有参数列表");
        }

        if (base) {
            Expression baseExpression = baseModel.addExpression(namePre + expCount);
            for (LpParam lpParam : params) {
                if (StringUtils.isBlank(lpParam.getVarName()) && lpParam.getVarIndex() == null) {
                    throw new LpParamException("参数不能为空");
                }

                // 索引
                int index;
                if (lpParam.getVarIndex() == null) {
                    index = nameIndex.get(lpParam.getVarName());
                } else {
                    index = lpParam.getVarIndex();
                }
                baseExpression.set(index, lpParam.getWeight());
            }

            if (lpExpression.getSign() == LpExpression.Sign.GT) {
                // 下限
                baseExpression.lower(lpExpression.getVal());
            } else if (lpExpression.getSign() == LpExpression.Sign.LT) {
                // 上限
                baseExpression.upper(lpExpression.getVal());
            } else {
                baseExpression.level(lpExpression.getVal());
            }


            Expression expression = model.addExpression(namePre + expCount);

            for (LpParam lpParam : params) {
                if (StringUtils.isBlank(lpParam.getVarName()) && lpParam.getVarIndex() == null) {
                    throw new LpParamException("参数不能为空");
                }

                // 索引
                int index;
                if (lpParam.getVarIndex() == null) {
                    index = nameIndex.get(lpParam.getVarName());
                } else {
                    index = lpParam.getVarIndex();
                }
                expression.set(index, lpParam.getWeight());
            }

            if (lpExpression.getSign() == LpExpression.Sign.GT) {
                // 下限
                expression.lower(lpExpression.getVal());
            } else if (lpExpression.getSign() == LpExpression.Sign.LT) {
                // 上限
                expression.upper(lpExpression.getVal());
            } else {
                expression.level(lpExpression.getVal());
            }


        } else {
            Expression expression = model.addExpression(namePre + expCount);

            for (LpParam lpParam : params) {
                if (StringUtils.isBlank(lpParam.getVarName()) && lpParam.getVarIndex() == null) {
                    throw new LpParamException("参数不能为空");
                }

                // 索引
                int index;
                if (lpParam.getVarIndex() == null) {
                    index = nameIndex.get(lpParam.getVarName());
                } else {
                    index = lpParam.getVarIndex();
                }
                expression.set(index, lpParam.getWeight());
            }

            if (lpExpression.getSign() == LpExpression.Sign.GT) {
                // 下限
                expression.lower(lpExpression.getVal());
            } else if (lpExpression.getSign() == LpExpression.Sign.LT) {
                // 上限
                expression.upper(lpExpression.getVal());
            } else {
                expression.level(lpExpression.getVal());
            }


        }

        //            printExp(lpExpression);
        if (lpExpressionMap.containsKey(namePre)) {
            lpExpressionMap.get(namePre).put(namePre + expCount, lpExpression);
        } else {
            Map<String, LpExpression> expressionMap = new HashMap<>();
            expressionMap.put(namePre + expCount, lpExpression);
            lpExpressionMap.put(namePre, expressionMap);
        }
        expCount++;

    }

    public void addExpression(LpExpression lpExpression) {
        addExpression(DEFAULT_EXP_NAME, lpExpression);
    }

    public void addExpressions(List<LpExpression> lpExpressions) {
        addExpressions(DEFAULT_EXP_NAME, lpExpressions);
    }

    public void addExpressions(String namePre, List<LpExpression> lpExpressions, boolean base) {
        for (LpExpression lpExpression : lpExpressions) {
            addExpression(namePre, lpExpression, base);
        }
    }

    public void addExpressions(String namePre, List<LpExpression> lpExpressions) {
        for (LpExpression lpExpression : lpExpressions) {
            addExpression(namePre, lpExpression);
        }
    }

    private void printCons() {
        List<Variable> variables = model.getVariables();
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (Variable variable : variables) {
            if (variable.getContributionWeight() == null) {
                continue;
            }
            double doubleValue = 0;
            try {
                doubleValue = variable.getContributionWeight().doubleValue();
            } catch (Exception e) {
                e.printStackTrace();
            }

            if (doubleValue == 0) {
                continue;
            }
            if (!first && doubleValue > 0) {
                sb.append(" + ");
            }
            first = false;
            sb.append(doubleValue).append(" ")
                    .append(variable.getName());
        }
        LOGGER.info(sb.toString());
//            System.out.println(sb.toString());
    }

    private void printVarNames() {
        List<Variable> variables = model.getVariables();

        for (Variable variable : variables) {
            LOGGER.info(variable.getName() + " ");
//                System.out.print(variable.getName() + " ");
        }

    }

    public void printExp(String expName, LpExpression expression) {
        StringBuilder sb = new StringBuilder();
        sb.append(expName).append(": ");
        boolean first = true;
        for (LpParam lpParam : expression.getParams()) {
            // 索引
            int index;
            if (lpParam.getVarIndex() == null) {
                index = nameIndex.get(lpParam.getVarName());
            } else {
                index = lpParam.getVarIndex();
            }
            String name = indexName.get(index);
            if (!first && lpParam.getWeight() >= 0) { // 不是第一个值且权重为正
                sb.append(" + ");
            }
            first = false;
            sb.append(lpParam.getWeight()).append(" ").append(name);
        }
        if (expression.getSign() == LpExpression.Sign.GT) {
            sb.append(" >= ");
        } else if (expression.getSign() == LpExpression.Sign.LT) {
            sb.append(" <= ");
        } else {
            sb.append(" = ");
        }
        sb.append(expression.getVal());
        LOGGER.info(sb.toString());
//            System.out.println(sb.toString());
    }

    public void printExp() {
        for (Map.Entry<String, Map<String, LpExpression>> entry : lpExpressionMap.entrySet()) {
            Map<String, LpExpression> expressionMap = entry.getValue();
            for (Map.Entry<String, LpExpression> expressionEntry : expressionMap.entrySet()) {
                printExp(expressionEntry.getKey(), expressionEntry.getValue());
            }

        }
    }

    public BigDecimal[] solve(Optimisation.Sense sense) {
        if (expCount == 0) {
            throw new LpSolveException("先设置条件");
        }

        if (curVarIndex == 0) {
            throw new LpSolveException("先设置参数");
        }

        ModelWrapper.setOptimisationSense(model, sense);
        ModelWrapper.setOptimisationSense(baseModel, sense);
//        try {
//            ModelWrapper.setOptimisationSense(model,sense);
//            Field field = model.getClass().getDeclaredField("myOptimisationSense");
//            field.setAccessible(true);
//            field.set(model, sense);
////                BeanUtils.setProperty(model,"myOptimisationSense", Optimisation.Sense.MIN);
////                PropertyUtils.setProperty(model,"myOptimisationSense", Optimisation.Sense.MIN);
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//
//        try {
//            Field field = baseModel.getClass().getDeclaredField("myOptimisationSense");
//            field.setAccessible(true);
//            field.set(baseModel, sense);
////                BeanUtils.setProperty(model,"myOptimisationSense", Optimisation.Sense.MIN);
////                PropertyUtils.setProperty(model,"myOptimisationSense", Optimisation.Sense.MIN);
//        } catch (Exception e) {
//            e.printStackTrace();
//        }

//            Optimisation.Result result = model.minimise();

//            printCons();
        printExp();
//            printVarNames();

        if (lpStrategy.preValidation(this)) {
            return lpStrategy.solve(this);
        } else {
            throw new RuntimeException("基础规则冲突，请检查");
        }


    }

    // 参数类型
    public enum VarType {
        // int类型
        INT,
        // 0，1类型
        BIN,
        // 不设置
        NONE
    }
}
